/*
 * Copyright (C) 2008 Universidade Federal de Campina Grande
 *  
 * This file is part of OurGrid. 
 *
 * OurGrid is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free 
 * Software Foundation, either version 3 of the License, or (at your option) 
 * any later version. 
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT 
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 * for more details. 
 * 
 * You should have received a copy of the GNU Lesser General Public License 
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * 
 */
package org.ourgrid.worker.controller;

import static org.ourgrid.worker.WorkerConstants.ENV_PLAYPEN;
import static org.ourgrid.worker.WorkerConstants.ENV_STORAGE;

import java.util.Map;
import java.util.concurrent.Future;

import org.ourgrid.broker.actions.ErrorOcurredMessageHandle;
import org.ourgrid.common.executor.Executor;
import org.ourgrid.common.executor.ExecutorException;
import org.ourgrid.common.executor.ExecutorHandle;
import org.ourgrid.common.interfaces.WorkerExecutionServiceClient;
import org.ourgrid.common.interfaces.to.GridProcessErrorTypes;
import org.ourgrid.reqtrace.Req;
import org.ourgrid.worker.WorkerComponent;
import org.ourgrid.worker.WorkerConstants;
import org.ourgrid.worker.controller.actions.BeginAllocationAction;
import org.ourgrid.worker.controller.actions.WorkerExecutionAction;
import org.ourgrid.worker.dao.EnvironmentDAO;
import org.ourgrid.worker.dao.GridProcessDAO;
import org.ourgrid.worker.dao.WorkAccountingDAO;
import org.ourgrid.worker.dao.WorkerStatusDAO;
import org.ourgrid.worker.messages.ExecutionControllerMessages;
import org.ourgrid.worker.receiver.WorkerExecutionClientReceiver;

import br.edu.ufcg.lsd.commune.container.servicemanager.ServiceManager;

public class ExecutionController {

	private static ExecutionController instance = null;
	
	@Req("REQ084")
	public static synchronized ExecutionController getInstance() {
		if (instance == null) {
			instance = new ExecutionController();
		}
		return instance;
	}
	
	public void beginAllocation(ServiceManager serviceManager) {
		
		serviceManager.getLog().debug(ExecutionControllerMessages.getBeginAllocationActionStartedMessage());
		
		deployExecutionClient(serviceManager);
		submitBeginAllocationAction(serviceManager);
	}

	private void submitBeginAllocationAction(ServiceManager serviceManager) {
		
		GridProcessDAO gridProcessDAO = serviceManager.getDAO(GridProcessDAO.class);
		Executor executor = gridProcessDAO.getExecutor();
		WorkerExecutionServiceClient executionClient = 
			(WorkerExecutionServiceClient) serviceManager.getObjectDeployment(
					WorkerConstants.WORKER_EXECUTION_CLIENT).getProxy();
		
		BeginAllocationAction beginAllocationAction = 
			new BeginAllocationAction(executor, executionClient);

		WorkerComponent workerComponent = (WorkerComponent) serviceManager.getApplication();
		Future<?> future = workerComponent.submitAction(beginAllocationAction);
		gridProcessDAO.setBeginAllocationFuture(future);
	}

	@Req("REQ084")
	public void scheduleCommand(long requestID, 
			Map<String, String> envVars, String command, String senderPublicKey, ServiceManager serviceManager) {
		
		GridProcessDAO gridProcessDAO = serviceManager.getDAO(GridProcessDAO.class);
		WorkerStatusDAO workerStatusDAO = serviceManager.getDAO(WorkerStatusDAO.class);
		
		envVars.put(WorkerConstants.ENV_PLAYPEN, serviceManager.getDAO(EnvironmentDAO.class).getPlaypenDir());
		envVars.put(WorkerConstants.ENV_STORAGE, serviceManager.getDAO(EnvironmentDAO.class).getStorageDir());
		
		if (gridProcessDAO.getCurrentHandle() == null && !gridProcessDAO.isExecutionFinish()) {
			
				if (workerStatusDAO.isAllocatedForRemotePeer()) {
					serviceManager.getDAO(WorkAccountingDAO.class).getActualWorkAccounting().startCPUTiming();
				}
				
				submitExecutionAction(envVars, command, serviceManager);
				
				serviceManager.getLog().info(ExecutionControllerMessages.getScheduleExecutionMessage(command, requestID, 
						envVars, senderPublicKey));
			
		} else {
			serviceManager.getLog().error(ExecutionControllerMessages.getConcurrentExecutionMethod(requestID, command, envVars));
			workerStatusDAO.getConsumer().sendMessage(
					new ErrorOcurredMessageHandle(new GridProcessError(GridProcessErrorTypes.CONCURRENT_RUNNING)));
		}
		
	}
	

	public String solvePlaypenAndStorage(String command, ServiceManager serviceManager) {
		if (command != null) {
			
			String newCommand = command;
			String playpenDir = getPlaypenDir(serviceManager);
			
			if (playpenDir != null) {
				newCommand = newCommand.replace( "$" + ENV_PLAYPEN, playpenDir);
			}
			
			String storageDir = getStorageDir(serviceManager);
			
			if (storageDir != null) {
				newCommand = newCommand.replace( "$" + ENV_STORAGE, storageDir);
			}
			
			return newCommand;
		}
		return command;
	}
	
	
	public void cancelActiveExecution(ServiceManager serviceManager) {
		
		GridProcessDAO executionDAO = serviceManager.getDAO(GridProcessDAO.class);
		Future<?> beginAllocationFuture = executionDAO.getBeginAllocationFuture();
		
		if (beginAllocationFuture != null && !beginAllocationFuture.isDone()) {
			serviceManager.getLog().debug(ExecutionControllerMessages.getBeginAllocationActionCancelledMessage());
			beginAllocationFuture.cancel(true);
		}
		
		ExecutorHandle currentHandle = executionDAO.getCurrentHandle();
		
		if (currentHandle != null) {
			
			executionDAO.getExecutionActionFuture().cancel(true);
			executionDAO.setCurrentHandle(null);

			try {
				executionDAO.getExecutor().kill(currentHandle);
			} catch (ExecutorException e) {
				//TODO log
			}
		}
	}
	
	public void executionFinish(boolean success, ServiceManager serviceManager) {
		GridProcessDAO gridProcessDAO = serviceManager.getDAO(GridProcessDAO.class);
		
		gridProcessDAO.setCurrentHandle(null);
		gridProcessDAO.setExecutionFinish(success);
	}

	public void executionIsRunning(ExecutorHandle handle, ServiceManager serviceManager) {
		serviceManager.getDAO(GridProcessDAO.class).setCurrentHandle(handle);
	}
	
	@Req("REQ084")
	private void submitExecutionAction(Map<String, String> envVars,
			String command, ServiceManager serviceManager) {
		
		GridProcessDAO gridProcessDAO = serviceManager.getDAO(GridProcessDAO.class);
		Executor executor = gridProcessDAO.getExecutor();
		
		WorkerExecutionServiceClient executionClient = 
			(WorkerExecutionServiceClient) serviceManager.getObjectDeployment(
					WorkerConstants.WORKER_EXECUTION_CLIENT).getProxy();
				
		WorkerExecutionAction workerExecutionAction = 
			new WorkerExecutionAction(envVars, command, executor, executionClient);

		WorkerComponent workerComponent = (WorkerComponent) serviceManager.getApplication();
		Future<?> future = workerComponent.submitAction(workerExecutionAction);
		gridProcessDAO.setExecutionFuture(future);
	}
	
	private String getPlaypenDir(ServiceManager serviceManager) {
		return serviceManager.getDAO(EnvironmentDAO.class).getPlaypenDir();
	}
	
	private String getStorageDir(ServiceManager serviceManager) {
		return serviceManager.getDAO(EnvironmentDAO.class).getStorageDir();
	}
	
	@Req("REQ084")
	private void deployExecutionClient(ServiceManager serviceManager) {
		if (!isExecutionClientDeployed(serviceManager)) {
			WorkerExecutionServiceClient executionClient = new WorkerExecutionClientReceiver();
			serviceManager.deploy(WorkerConstants.WORKER_EXECUTION_CLIENT, executionClient);
		}
	}

	private boolean isExecutionClientDeployed(ServiceManager serviceManager) {
		return serviceManager.getObjectDeployment(WorkerConstants.WORKER_EXECUTION_CLIENT) != null;
	}
	
}
